/*
 * Copyright 2007-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.paoding.rose;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.paoding.rose.impl.scanning.ModuleScanner;
import net.paoding.rose.impl.scanning.ModuleScannerImpl;
import net.paoding.rose.impl.util.SpringUtils;
import net.paoding.rose.web.RequestPath;
import net.paoding.rose.web.impl.context.ContextLoader;
import net.paoding.rose.web.impl.mapping.Mapping;
import net.paoding.rose.web.impl.module.ControllerInfo;
import net.paoding.rose.web.impl.module.Module;
import net.paoding.rose.web.impl.module.ModulesBuilder;
import net.paoding.rose.web.impl.module.NamedControllerInterceptor;
import net.paoding.rose.web.impl.thread.Engine;
import net.paoding.rose.web.impl.thread.InvocationBean;
import net.paoding.rose.web.paramresolver.Resolver;
import net.paoding.rose.web.var.PrivateVar;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import org.springframework.web.util.NestedServletException;
import org.springframework.web.util.WebUtils;

/**
 * Paoding Rose是一个MVC开发框架(这里的MVC概念更接近于Grails/RoR的MVC，而非传统的Java
 * MVC，前者范畴较广)。使用Rose框架开发，除了将<strong>paoding-rose.jar</strong>
 * 以及依赖的jar拷贝到classpath下，剩下的最大一项工作就是配置{@link RoseFilter}。
 * <p>
 * 您，作为Rose的使用者或评估者，您只需要按照下面介绍的方式将 {@link RoseFilter}
 * 拷贝配置在web.xml中，用以拦截<strong>所有</strong>的web请求即可。虽然{@link RoseFilter}
 * 会拦截所有请求，但是Rose能够判断所拦截的请求是否应该由对应的Controller控制器来处理(详见Referentce),
 * 如果Rose判断该请求不应在rose处理范围内，则 {@link RoseFilter}会让他“通过”不做任何处理。
 * <p>
 * 因为Rose使用Filter而非Servlet来接收并转发请求给控制器Controller，所以这里的配置必须满足一个很重要的注意点：
 * 即Rose过滤器必须配置在所有过滤器的
 * <strong>最后</strong>。只有这样，才能保证所有应该由控制器处理的web请求都能够通过其他可能的Filter拦截。
 * <p>
 * 绝大多数情况，按以下配置即可：
 * 
 * <pre>
 * 	&lt;filter&gt;
 * 		&lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
 * 		&lt;filter-class&gt;net.paoding.rose.RoseFilter&lt;/filter-class&gt;
 * 	&lt;/filter&gt;
 * 
 * 	&lt;filter-mapping&gt;
 * 		&lt;filter-name&gt;roseFilter&lt;/filter-name&gt;
 * 		&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
 * 		&lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
 * 		&lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
 * 		&lt;dispatcher&gt;INCLUDE&lt;/dispatcher&gt;
 * 	&lt;/filter-mapping&gt;
 * 
 * </pre>
 * 
 * 再次重复一下稍微需要注意的地方：<br>
 * 1)如上所说的<strong>filter-mapping</strong>必须配置在所有Filter Mapping的最后。
 * 2)不能将以上dispatcher的forward去掉，否则forward的请求Rose框架将拦截不到。include也是如此<br>
 * <p>
 * 
 * @author zhiliang.wang [qieqie.wang@paoding.net]
 */
public class RoseFilter extends GenericFilterBean {

    /** 默认的applicationContext地址 */
    public static final String DEFAULT_CONTEXT_CONFIG_LOCATION = ""
            + "/WEB-INF/applicationContext*.xml" + ",classpath:applicationContext*.xml"
            + ",classpath-jar:applicationContext*.xml";

    /**
     * @param request
     * @return
     */
    public static boolean isLazyEnd(ServletRequest request) {
        Boolean done = (Boolean) request.getAttribute("$$paoding-rose.lazyEnd");
        return ((done != null) && done);
    }

    /** 使用的applicationContext地址，大部分情况下留空就好，按照惯例开发，Rose会处理好的 */
    private String contextConfigLocation;

    /** Spring应用上下文环境装载对象，把它记录下来在destroy的时候对它进行close */
    private ContextLoader contextLoader;

    /** web模块 */
    private List<Module> modules = Collections.unmodifiableList(new ArrayList<Module>(0));

    /** 所有的请求被RoseFilter过滤时，基本上都将转到该RoseEngine处理 */
    private Engine engine;

    /**
     * 改变默认行为，告知Rose要读取的applicationContext地址
     */
    public void setContextConfigLocation(final String contextConfigLocation) {
        this.contextConfigLocation = contextConfigLocation;
    }

    public Engine getEngine() {
        return engine;
    }

    @Override
    protected final void initFilterBean() throws ServletException {
        // 把servletContext收藏成起来
        PrivateVar.servletContext(getServletContext());

        // 如果没有通过Filter的init参数配置contextConfigLocation的话，
        // 则看看是否使用Spring要求的方式配置了contextConfigLocation没有?
        if (StringUtils.isBlank(contextConfigLocation)) {
            contextConfigLocation = getServletContext().getInitParameter("contextConfigLocation");
        }

        // 如果Filter和Context都没有配置contextConfigLocation，那就使用默认配置了
        // 我们建议开发者兄弟们，其实您为什么要配置呢？除非特殊情况，否则就使用惯例好了，是吧？大家都轻松
        if (StringUtils.isBlank(contextConfigLocation)) {
            contextConfigLocation = DEFAULT_CONTEXT_CONFIG_LOCATION;
        }

        try {
            // 先识别并初始化中间层、资源层(数据库连接、其他资源连接等等)：
            // 如果applicationContext已经被其他程序初始化过的话，Rose自动使用它，否则Rose自行初始化它
            if (Rc.getRootWebApplicationContext() == null) {
                contextLoader = new ContextLoader(contextConfigLocation);
                contextLoader.initWebApplicationContext(getServletContext());
            }

            // 然后自动扫描识别并初始化Controller控制器:
            // controller类应该放到package为xxx.controllers或其直接子package下(
            // 并以Controller结尾或标注@net.paoding.rose.web.annotation.Controller)
            // 按照这样的约定，Rose就能自动“挑”出他们出来。
            ModuleScanner moduleScanner = (ModuleScanner) SpringUtils.getBean(Rc
                    .getRootWebApplicationContext(), ModuleScanner.class);
            if (moduleScanner == null) {
                moduleScanner = (ModuleScanner) SpringUtils.createBean(ModuleScannerImpl.class, Rc.getRootWebApplicationContext());
            }
            modules = new ModulesBuilder().build(Rc.getRootWebApplicationContext(), moduleScanner);
            engine = new RoseEngine(modules);
            if (logger.isInfoEnabled()) {
                final StringBuilder sb = new StringBuilder(4096);
                dumpModules(modules, sb);
                logger.info(sb.toString());
            }
            // 控制台提示
            System.out.println("Rose Initiated (version=0.9)");
            logger.info("Rose Initiated (version=0.9)");
        } catch (final Exception e) {
            throw new NestedServletException(e.getMessage(), e);
        }
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
            final FilterChain filterChain) throws IOException, ServletException {
        // cast
        final HttpServletRequest httpRequest = (HttpServletRequest) request;
        final HttpServletResponse httpResponse = (HttpServletResponse) response;

        // debug
        if (logger.isDebugEnabled()) {
            logger.debug(httpRequest.getMethod() + " " + httpRequest.getRequestURL());
        }

        // 创建RequestPath对象，用于记录对地址解析的结果
        final RequestPath requestPath = new RequestPath();
        initRequestPath(httpRequest, requestPath);

        // 构造invocation对象，一个invocation对象用于封装和本次请求有关的匹配结果以及方法调用参数
        InvocationBean invocation = new InvocationBean();
        invocation.setRequest(httpRequest);
        invocation.setResponse(httpResponse);
        invocation.setRequestPath(requestPath);

        // 
        if (!engine.match(invocation)) {
            if (logger.isDebugEnabled()) {
                logger.debug("not rose uri: " + requestPath.getUri());
            }
            // 调用其它Filter
            filterChain.doFilter(httpRequest, httpResponse);
        } else {
            try {
                engine.invoke(invocation);
            } catch (Throwable exception) {
                //
                StringBuilder sb = new StringBuilder(1024);
                sb.append("error happended for request:").append(requestPath.getMethod());
                sb.append(" ").append(requestPath.getUri()).append("->");
                sb.append(invocation.getActionEngine()).append(" params=");
                sb.append(Arrays.toString(invocation.getMethodParameters()));
                ServletException servletException = new NestedServletException(sb.toString(),
                        exception);
                //
                logger.error(servletException);
                //
                throw servletException;
            }
        }
    }

    private void initRequestPath(final HttpServletRequest request, final RequestPath requestPath) {
        requestPath.setMethod(request.getMethod());
        if (WebUtils.isIncludeRequest(request)) {
            requestPath.setIncludeRequest(true);
            // 对include而言，request.getRequestURI()是原来的地址目标的地址，
            // INCLUDE_REQUEST_URI_ATTRIBUTE属性的值才是目标的地址
            // http://www.bj-hk.go.cn/resin-doc/webapp/faq.xtp#include
            requestPath.setUri((String) request
                    .getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE));
        } else {
            requestPath.setUri(request.getRequestURI());
        }
        requestPath.setCtxpath(request.getContextPath());
        if (requestPath.getCtxpath().length() <= 1) {
            requestPath.setPathInfo(requestPath.getUri());
        } else {
            requestPath.setPathInfo(requestPath.getUri().substring(
                    requestPath.getCtxpath().length()));
        }
    }

    @Override
    public void destroy() {
        if (contextLoader != null) {
            contextLoader.closeWebApplicationContext(getServletContext());
        }
        try {
            engine.destroy();
        } catch (Exception e) {
            logger.error("", e);
        }
        super.destroy();
    }

    //----------

    // 后续可以提取出来放到什么地方，是不是采用模板语言来定义?
    private void dumpModules(final List<Module> modules, final StringBuilder sb) {
        sb.append("\n--------Modules(Total ").append(modules.size()).append(")--------");
        sb.append("\n");
        for (int i = 0; i < modules.size(); i++) {
            final Module module = modules.get(i);
            sb.append("module ").append(i + 1).append(":\n");
            sb.append("\tpath='").append(module.getPath());
            sb.append("';\n\tcontrollers=[");
            final List<Mapping<ControllerInfo>> controllerMappings = module.getControllerMappings();
            for (final Mapping<ControllerInfo> mapping : controllerMappings) {
                sb.append("'").append(mapping.getPath()).append("'=").append(
                        mapping.getTarget().getControllerClass().getSimpleName()).append(", ");
            }
            if (!controllerMappings.isEmpty()) {
                sb.setLength(sb.length() - 2);
            }
            sb.append("];\n\tparamResolvers=[");
            for (Resolver resolver : module.getCustomerResolvers()) {
                sb.append(resolver.getClass().getSimpleName()).append(", ");
            }
            if (module.getCustomerResolvers().size() > 0) {
                sb.setLength(sb.length() - 2);
            }
            sb.append("];\n\tinterceptors=[");
            for (NamedControllerInterceptor interceptor : module.getInterceptors()) {
                sb.append(interceptor.getName()).append("(").append(interceptor.getPriority())
                        .append("), ");
            }
            if (module.getInterceptors().length > 0) {
                sb.setLength(sb.length() - 2);
            }
            sb.append("];\n\terrorHander=").append(
                    module.getErrorHandler() == null ? "<null>" : module.getErrorHandler());
            final Mapping<ControllerInfo> def = module.getDefaultController();
            sb.append(";\n\tdefaultController=").append(def == null ? "<null>" : def.getPath());
            sb.append("\n\n");
        }
        sb.append("--------end--------");
    }

}
